package it.unibo.refolding.pico.ui;

import it.unibo.refolding.common.CommonThings;
import it.unibo.refolding.pico.ApproachRetractionLoop;
import it.unibo.refolding.pico.ContactPointAndSlopeHelper;
import it.unibo.refolding.pico.FileFormat;
import it.unibo.refolding.pico.Parsed;
import it.unibo.refolding.pico.PeakSlopes;
import it.unibo.refolding.pico.PeakSlopesHelper;
import it.unibo.refolding.pico.PicoDataAnalyser;
import it.unibo.refolding.pico.PicoDataAnalyser.ContactPointAndSlope;
import it.unibo.refolding.pico.PicoShapes;
import it.unibo.refolding.pico.PicoUtil;
import it.unibo.refolding.pico.stripchart.AnalysisOptions;
import it.unibo.refolding.pico.stripchart.JpHelper;
import it.unimi.dsi.fastutil.doubles.DoubleLists;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.KeyEvent;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.ButtonGroup;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JRadioButton;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.Parser;
import org.apache.commons.lang3.StringUtils;

import com.google.common.base.Preconditions;

/**
 * Warning: After changing the Z position of the contact point in the .cps file,
 * the .psi file becomes invalid!
 */
public class PeakSlopesMain {

  private static final Logger logger = Logger.getLogger(PeakSlopesMain.class.getSimpleName());

  private static JInternalFrame createFrame(PeakSlopesHolder h) {
    JInternalFrame internalFrame = new JInternalFrame(h.filename(), true, true, true, true);
    internalFrame.setVisible(true);
    return internalFrame;
  }

  protected void quit() {
    System.exit(0);
  }

  /**
   * Create the GUI and show it. For thread safety, this method should be
   * invoked from the event-dispatching thread.
   * 
   * @param hs
   */
  private static void createAndShowGUI(List<PeakSlopesHolder> hs, double defScale) {
    JFrame frame = new JFrame();
    frame.setUndecorated(false);
    frame.getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
    int inset = 50;
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    frame.setBounds(inset, inset, screenSize.width - inset * 2, screenSize.height - inset * 2);
    JDesktopPane desktop = new JDesktopPane();
    int maxSegments = 0;
    for (PeakSlopesHolder h : hs) {
      maxSegments = Math.max(maxSegments, h.loopCount());
    }
    int k = maxSegments * (ApproachRetractionLoop.APPROACH_RETRACTION_SEGMENTS + 1);
    List<Color> colors = PicoShapes.colorsForPieces(1 + k);
    List<Color> retractionColors = PicoShapes.invert(CommonThings.slice(colors, 3, colors.size(), 4));
    for (int i = 0; i < hs.size(); ++i) {
      PeakSlopesHolder h = hs.get(i);
      JInternalFrame internalFrame = createFrame(h);
      internalFrame.setLocation(i, i);
      PeakSlopesDrawer peakSlopesDrawer = new PeakSlopesDrawer(defScale, h, colors.get(0), retractionColors);
      PeakSlopesZDefPanel panel = new PeakSlopesZDefPanel(new ZDefDrawer(
        h, peakSlopesDrawer.defScale, colors), h, peakSlopesDrawer);
      PeakSlopesZDefListener listener = new PeakSlopesZDefListener(panel, peakSlopesDrawer);
      panel.addMouseListener(listener);
      panel.addMouseMotionListener(listener);
      UiUtil.addKeyShortcutForClosing(internalFrame, panel);
      JScrollPane scrollPane = new JScrollPane(panel);
      scrollPane.getHorizontalScrollBar().setUnitIncrement(16);
      scrollPane.getVerticalScrollBar().setUnitIncrement(16);
      internalFrame.getContentPane().setLayout(new BorderLayout());
      if (h.loopCount() > 1) {
        JToolBar toolbar = new JToolBar();
        ButtonGroup group = new ButtonGroup();
        for (int j = 0; j < h.loopCount(); ++j) {
          JRadioButton button = new JRadioButton(Integer.toString(j));
          button.addActionListener(listener.curveSelectionListener());
          toolbar.add(button);
          button.setSelected(j == 0);
          button.setMnemonic(KeyEvent.VK_0 + j);
          group.add(button);
        }
        internalFrame.getContentPane().add(toolbar, BorderLayout.PAGE_START);
      }
      internalFrame.getContentPane().add(scrollPane, BorderLayout.CENTER);
      desktop.add(internalFrame);
      try {
        internalFrame.setMaximum(true);
      } catch (PropertyVetoException e) {
        throw new RuntimeException(e.getMessage(), e);
      }
    }
    frame.setContentPane(desktop);
    // Make dragging a little faster but perhaps uglier.
    desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
    frame.setVisible(true);
  }

  public static void main(String[] args) throws ParseException, IllegalArgumentException,
    SecurityException, IllegalAccessException, NoSuchFieldException, IOException {
    Options options = new Options();

    Option dataDirOption = new Option("dataDir", true, "directory with stripchart files");
    dataDirOption.setRequired(true);
    dataDirOption.setType(String.class);
    options.addOption(dataDirOption);

    Option inputPathListOption = PicoUtil.createInputPathListOption();
    options.addOption(inputPathListOption);

    Option wildcardPatternOption = new Option("wildcardPattern", true, "wildcardPattern for files");
    wildcardPatternOption.setRequired(true);
    wildcardPatternOption.setType(String.class);
    options.addOption(wildcardPatternOption);

    Option defScaleOption = new Option("defScale", true, "defScale for files");
    defScaleOption.setRequired(true);
    options.addOption(defScaleOption);

    Option fileFormatOption = AnalysisOptions.createFileFormatOption();
    options.addOption(fileFormatOption);

    Option contactPointAndSlopeDirOption = ContactPointAndSlopeHelper.createContactPointAndSlopeDirOption();
    options.addOption(contactPointAndSlopeDirOption);

    Option junctionPointsDirOption = JpHelper.createJunctionPointsOption();
    options.addOption(junctionPointsDirOption);

    Option peakSlopesDirOption = PeakSlopesHelper.createPeakSlopesDirOption();
    options.addOption(peakSlopesDirOption);

    Parser parser = new GnuParser();
    CommandLine line = parser.parse(options, args);
    String dataDir = line.getOptionValue(dataDirOption.getOpt());
    String inputPathList = line.getOptionValue(inputPathListOption.getOpt());
    Preconditions.checkArgument(
      StringUtils.isEmpty(dataDir) ^ StringUtils.isEmpty(inputPathList),
      "Must specify exactly one of dataDir and inputPathList.");
    String wildcardPattern = line.getOptionValue(wildcardPatternOption.getOpt());
    FileFormat fileFormat = FileFormat.valueOf(line.getOptionValue(fileFormatOption.getOpt()).toUpperCase(
      Locale.ROOT));
    final double defScale = Double.parseDouble(line.getOptionValue(defScaleOption.getOpt()));
    String contactPointAndSlopeDir = line.getOptionValue(contactPointAndSlopeDirOption.getOpt());
    String peakSlopesDir = line.getOptionValue(peakSlopesDirOption.getOpt());
    String junctionPointsDirAsString = line.getOptionValue(junctionPointsDirOption.getOpt());
    Path junctionPointsDir = StringUtils.isEmpty(junctionPointsDirAsString) ? null
      : Paths.get(junctionPointsDirAsString);

    Path contactPointAndSlopeDirectory = Paths.get(contactPointAndSlopeDir);
    Path peakSlopesDirectory = Paths.get(peakSlopesDir);
    CommonThings.createDirectoryPathIfNeeded(peakSlopesDirectory);

    List<Path> files = PicoUtil.computeFileList(dataDir, inputPathList, wildcardPattern);

    final List<PeakSlopesHolder> hs = new ArrayList<>();
    for (Path file : files) {
      final Parsed p = PicoUtil.parse(file, fileFormat, null, null, DoubleLists.EMPTY_LIST, junctionPointsDir);
      if (!p.picoDataList.isEmpty()) {
        Path cpsFile = ContactPointAndSlopeHelper.contactPointAndSlopeFile(
          contactPointAndSlopeDirectory, file.getFileName().toString());
        ContactPointAndSlope cps = ContactPointAndSlopeHelper.extractContactPointAndSlope(cpsFile);
        Path peakSlopesFile = PeakSlopesHelper.peakSlopesFile(
          peakSlopesDirectory, file.getFileName().toString());
        PeakSlopes prototype = PeakSlopes.of(new int[] {});
        final List<PeakSlopes> peakSlopes;
        if (Files.isRegularFile(peakSlopesFile)) {
          List<PeakSlopes> ps = PeakSlopesHelper.extractPeakSlopesList(peakSlopesFile);
          peakSlopes = PeakSlopesHelper.fillUpPeakSlopes(p.picoDataList, prototype, ps);
        } else {
          peakSlopes = Collections.unmodifiableList(Arrays.asList(prototype.deepCopies(
            p.picoDataList.size())));
        }
        int[] offsets = new int[peakSlopes.size()];
        for (int i = 0; i < peakSlopes.size(); ++i) {
          offsets[i] = PicoDataAnalyser.offsetOfEndingContiguousRegionNotBeforeContactPoint(
            p.picoDataList.get(i).sharedRetraction().sharedZArray(), cps.zLsb);
        }
        PeakSlopesHolder h = new PeakSlopesHolder(
          peakSlopesFile, p.z, p.def, cps.zLsb, offsets, peakSlopes, p.jp);
        hs.add(h);
      }
    }
    logger.log(Level.INFO, "Curves: {0}", hs.size());
    if (!hs.isEmpty()) {
      javax.swing.SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          createAndShowGUI(hs, defScale);
        }
      });
    }
  }

  
  
  /*
   * TODO: Select them CIELAB:
   * http://svn.apache.org/repos/asf/xmlgraphics/commons/trunk/src/java/org/apache/xmlgraphics/java2d/color/CIELabColorSpace.java
   */
  /*
  private static Color acqua = new Color(0x00FFFF);
  private static Color black  = new Color(0x000000);
  private static Color blue = new Color(0x0000FF);
  private static Color cyan = new Color(0x00FFFF);
  private static Color fuchsia = new Color(0xFF00FF);
  private static Color gold = new Color(0xFFD700);
  private static Color grey = new Color(0x808080);
  private static Color lime = new Color(0x00FF00);
  private static Color green = new Color(0x008000);
  private static Color maroon = new Color(0x800000);
  
  private static final Color[] colors = {Color.BLACK, Color.YELLOW, Color.BLUE, Color.ORANGE, Color.RED, fuchsia, Color.MAGENTA, maroon, Color.PINK, gold, lime, Color.GREEN,
    Color.CYAN, acqua};
    */
}
